//	VirtualDub - Video processing and capture application
//	System library component
//	Copyright (C) 1998-2004 Avery Lee, All Rights Reserved.
//
//	Beginning with 1.6.0, the VirtualDub system library is licensed
//	differently than the remainder of VirtualDub.  This particular file is
//	thus licensed as follows (the "zlib" license):
//
//	This software is provided 'as-is', without any express or implied
//	warranty.  In no event will the authors be held liable for any
//	damages arising from the use of this software.
//
//	Permission is granted to anyone to use this software for any purpose,
//	including commercial applications, and to alter it and redistribute it
//	freely, subject to the following restrictions:
//
//	1.	The origin of this software must not be misrepresented; you must
//		not claim that you wrote the original software. If you use this
//		software in a product, an acknowledgment in the product
//		documentation would be appreciated but is not required.
//	2.	Altered source versions must be plainly marked as such, and must
//		not be misrepresented as being the original software.
//	3.	This notice may not be removed or altered from any source
//		distribution.

#include "stdafx.h"
#include <new>

#include <windows.h>
#include <mmsystem.h>

#include <vd2/system/time.h>
#include <vd2/system/thread.h>

#ifdef _MSC_VER
	#pragma comment(lib, "winmm")
#endif

uint32 VDGetCurrentTick() {
	return (uint32)GetTickCount();
}

uint64 VDGetPreciseTick() {
	LARGE_INTEGER li;
	QueryPerformanceCounter(&li);
	return li.QuadPart;
}

namespace {
	uint64 VDGetPreciseTicksPerSecondNowI() {
		LARGE_INTEGER freq;
		QueryPerformanceFrequency(&freq);
		return freq.QuadPart;
	}

	double VDGetPreciseTicksPerSecondNow() {
		LARGE_INTEGER freq;
		QueryPerformanceFrequency(&freq);
		return (double)freq.QuadPart;
	}
}

uint64 VDGetPreciseTicksPerSecondI() {
	static uint64 ticksPerSecond = VDGetPreciseTicksPerSecondNowI();

	return ticksPerSecond;
}

double VDGetPreciseTicksPerSecond() {
	static double ticksPerSecond = VDGetPreciseTicksPerSecondNow();

	return ticksPerSecond;
}

double VDGetPreciseSecondsPerTick() {
	static double secondsPerTick = 1.0 / VDGetPreciseTicksPerSecondNow();

	return secondsPerTick;
}

uint32 VDGetAccurateTick() {
	return timeGetTime();
}

///////////////////////////////////////////////////////////////////////////////
VDCallbackTimer::VDCallbackTimer()
	: mTimerAccuracy(0)
{
}

VDCallbackTimer::~VDCallbackTimer() {
	Shutdown();
}

bool VDCallbackTimer::Init(IVDTimerCallback *pCB, uint32 period_ms) {
	return Init2(pCB, period_ms * 10000);
}

bool VDCallbackTimer::Init2(IVDTimerCallback *pCB, uint32 period_100ns) {
	Shutdown();

	mpCB = pCB;
	mbExit = false;
	UINT accuracy = period_100ns / 20000;
	if (accuracy > 10)
		accuracy = 10;

	TIMECAPS tc;
	if (TIMERR_NOERROR == timeGetDevCaps(&tc, sizeof tc)) {
		if (accuracy < tc.wPeriodMin)
			accuracy = tc.wPeriodMin;
		if (accuracy > tc.wPeriodMax)
			accuracy = tc.wPeriodMax;
	}

	if (TIMERR_NOERROR == timeBeginPeriod(accuracy)) {
		mTimerAccuracy = accuracy;
		mTimerPeriod = period_100ns;
		mTimerPeriodAdjustment = 0;
		mTimerPeriodDelta = 0;

		if (ThreadStart())
			return true;
	}

	Shutdown();

	return false;
}

void VDCallbackTimer::Shutdown() {
	if (isThreadActive()) {
		mbExit = true;
		msigExit.signal();
		ThreadWait();
	}

	if (mTimerAccuracy) {
		timeEndPeriod(mTimerAccuracy);
		mTimerAccuracy = 0;
	}
}

void VDCallbackTimer::SetRateDelta(int delta_100ns) {
	mTimerPeriodDelta = delta_100ns;
}

void VDCallbackTimer::AdjustRate(int adjustment_100ns) {
	mTimerPeriodAdjustment += adjustment_100ns;
}

bool VDCallbackTimer::IsTimerRunning() const {
	return const_cast<VDCallbackTimer *>(this)->isThreadActive();
}

void VDCallbackTimer::ThreadRun() {
	uint32 timerPeriod = mTimerPeriod;
	uint32 periodHi = timerPeriod / 10000;
	uint32 periodLo = timerPeriod % 10000;
	uint32 nextTimeHi = VDGetAccurateTick() + periodHi;
	uint32 nextTimeLo = periodLo;

	uint32 maxDelay = mTimerPeriod / 2000;

	SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);

	HANDLE hExit = msigExit.getHandle();

	while(!mbExit) {
		uint32 currentTime = VDGetAccurateTick();
		sint32 delta = nextTimeHi - currentTime;

		if (delta > 0) {
			// safety guard against the clock going nuts
			DWORD res;
			if ((uint32)delta > maxDelay)
				res = ::WaitForSingleObject(hExit, maxDelay);
			else
				res = ::WaitForSingleObject(hExit, nextTimeHi - currentTime);

			if (res != WAIT_TIMEOUT)
				break;
		}

		if ((uint32)abs(delta) > maxDelay) {
			nextTimeHi = currentTime + periodHi;
			nextTimeLo = periodLo;
		} else {
			nextTimeLo += periodLo;
			nextTimeHi += periodHi;
			if (nextTimeLo >= 10000) {
				nextTimeLo -= 10000;
				++nextTimeHi;
			}
		}

		mpCB->TimerCallback();

		int adjust = mTimerPeriodAdjustment.xchg(0);
		int perdelta = mTimerPeriodDelta;

		if (adjust || perdelta) {
			timerPeriod += adjust;
			periodHi = (timerPeriod+perdelta) / 10000;
			periodLo = (timerPeriod+perdelta) % 10000;
		}
	}
}
